use ndkm::{Grapher, Window as ANWin};
use std::{
	cell::{Cell, RefCell},
	collections::HashMap,
	rc::Rc,
};
use thunderdome::{Arena, Index};

use crate::Window;

use super::WinRef;

pub type RcDynWinDelegate = Rc<dyn types::WinRep<Window>>;

pub struct WinFrame(pub Key);
impl ndkm::FrameDelegate for WinFrame {
	fn on_frame(&self, frame_time_nanos: i64) {
		// log::info!("on_frame {}", (frame_time_nanos / 1000000) % 1000);
		with_win(self.0, |win| {
			win.nanos.set(frame_time_nanos);
			if win.dirty.get() && win.anwin().is_some() {
				win.dirty.set(false);
				// log::info!("on_frame with dirty");
				win.rep()?.on_req_draw(win.win_ref(), win.frame_nano());
			}
			Some(())
		});
	}
}

pub struct Win {
	pub(super) key_: String,
	rep: Option<RcDynWinDelegate>,
	anwin: Option<ANWin>,
	pub(super) scale_factor: Option<f32>,
	grapher: Grapher<WinFrame>,
	frame: Option<Rc<WinFrame>>,
	dirty: Cell<bool>,
	nanos: Cell<i64>,
	// instant: std::time::Instant,
}
impl Win {
	pub fn win_ref(&'_ self) -> WinRef<'_> {
		WinRef(self.anwin.as_ref(), self)
	}
	pub fn rep(&self) -> Option<&RcDynWinDelegate> {
		self.rep.as_ref()
	}
	pub fn anwin(&self) -> Option<&ANWin> {
		self.anwin.as_ref()
	}
	pub fn dirty(&self, src: &str) {
		if false == self.dirty.get() {
			log::debug!("{} dirtied by {src}", &self.key_);
			self.dirty.set(true);
			if let Some(frame) = &self.frame {
				self.grapher.frame(frame)
			}
		}
	}
	pub fn frame_ms(&self) -> i64 {
		const NANOS_PER_MS: i64 = 1_000_000;
		self.nanos.get() / NANOS_PER_MS
	}
	pub fn frame_nano(&self) -> i64 {
		// self.instant.elapsed().as_nanos() as _
		self.nanos.get()
	}
}

pub type Key = Index;
thread_local! {
	static CACHE:RefCell<Arena<Win>> = Default::default();
	static REG:RefCell<HashMap<String,RcDynWinDelegate>>=Default::default();
}

pub fn reg_window(name: impl AsRef<str>, rep: RcDynWinDelegate) {
	REG.with_borrow_mut(|regs| {
		regs.insert(name.as_ref().to_string(), rep);
	})
}
pub fn new_window(name: impl AsRef<str>) -> Key {
	// TODO check main thread
	let rep = REG.with_borrow(|regs| regs.get(name.as_ref()).cloned());
	let key = name.as_ref();
	CACHE.with_borrow_mut(|vars| {
		for (idx, win) in vars.iter() {
			if win.key_ == key.as_ref() {
				log::warn!("Duplicated Activity with same Window");
				return idx;
			}
		}
		let key = key.to_string();
		let key = vars.insert(Win {
			key_: key.clone(),
			rep,
			anwin: None,
			scale_factor: None,
			grapher: Grapher::<WinFrame>::singleton(),
			frame: None,
			dirty: Cell::new(false),
			nanos: Cell::new(0),
			// instant: std::time::Instant::now(),
		});
		let frame = Rc::new(WinFrame(key.clone()));
		if let Some(win) = vars.get_mut(key) {
			win.frame = Some(frame);
		}
		key
	})
}
pub fn del_window(key: Key) {
	// TODO check main thread
	CACHE.with_borrow_mut(|vars| {
		let ret = vars.remove(key);
		std::mem::drop(ret);
	})
}
pub(crate) fn replace_anwin(key: Key, mut awin: Option<ndkm::Window>) -> Option<ndkm::Window> {
	with_win_mut(key, |win| {
		if let Some(awin) = awin.take() {
			win.anwin.replace(awin)
		} else {
			win.anwin.take()
		}
	})
}
pub(crate) fn with_win_mut<T>(key: Key, mut fun: impl FnMut(&mut Win) -> Option<T>) -> Option<T> {
	CACHE.with_borrow_mut(|vars| vars.get_mut(key).map(|win| fun(win)).flatten())
}
pub(crate) fn with_win<T>(key: Key, mut fun: impl FnMut(&Win) -> Option<T>) -> Option<T> {
	CACHE.with_borrow(|vars| vars.get(key).map(|win| fun(win)).flatten())
}
pub(crate) fn with_wins<T>(fun: impl FnOnce(&Arena<Win>) -> T) -> T {
	CACHE.with_borrow(|vars: &Arena<Win>| fun(vars))
}
